package com.gotobject.junit.runners;

import com.gotobject.junit.ConfigureWith;
import com.gotobject.junit.internal.builders.PerformanceRunnerBuilder;
import com.gotobject.junit.internal.runners.model.Explosion;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 *  todo: need to implement this. HERE as of JAN 24, 2009 to add the runnerListener
 * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
 */
public class PerformanceSuite extends Suite {
    private static final XmlUtil XML_UTIL = new XmlUtil();
    public PerformanceSuite(Class<?> klass) throws InitializationError {
		super(new PerformanceRunnerBuilder(configuration(klass)), klass, inferredClasses());
    }

	private static Map<Class<?>, Explosion> configuration(Class<?> forClass) throws InitializationError {
		return XML_UTIL.use(forClass).getConfigurations();
	}

	private static Class<?>[] inferredClasses(){
		return XML_UTIL.getInferredClasses();
	}

	/**
     * utility to load classes listed in a properties file.
     */
    private static class XmlUtil {
        private final Map<Class<?>, Explosion> deck = new HashMap<Class<?>, Explosion>();
        private List<Class<?>> inferredClasses;
		private Class<?> root;
		XmlUtil(){
			inferredClasses = new ArrayList<Class<?>>();
			root			= null;
		}

        XmlUtil use(Class<?> klass) throws InitializationError {
			if(root != null && root.equals(klass)){
				return this;
			}
			inferredClasses.addAll(new ArrayList<Class<?>>(inferClasses(klass)));
            return this;
        }


        private static Explosion formatKey(String key){
            final String[]  stmnt   = key.split(":");
            final String    type    = stmnt[0];

            final int   users = Integer.valueOf(stmnt[1]);
            final int   itr   = Integer.valueOf(stmnt[2]);
            final long  resp  = isLoad(stmnt) ? 0L : Long.valueOf(stmnt[3]);
            return new Explosion(users, itr, resp).forTest(type);
        }

        private static boolean isLoad(String[] statement){
            return statement.length == 3;
        }

        public Map<Class<?>, Explosion> getConfigurations(){
            return new HashMap<Class<?>, Explosion>(deck);
        }

        Class<?>[] getInferredClasses(){
            return inferredClasses.toArray(new Class<?>[inferredClasses.size()]);
        }

        private List<Class<?>> inferClasses(Class<?> klass) throws InitializationError {
			root = klass;
			final ConfigureWith config      = klass.getAnnotation(ConfigureWith.class);
            final String        fileName    = config.value();
            final List<Class<?>> capsule;
            try {
                final Properties xmlProps    = loadXml(klass, fileName);
                capsule    = new ArrayList<Class<?>>(xmlProps.size());
                for(Object each : xmlProps.keySet()){
                    final String        key     = String.class.cast(each);
                    final Explosion     cfg     = formatKey(key);
                    final Class<?>      target  = Class.forName(xmlProps.getProperty(key).trim());
                    capsule.add(target);
                    deck.put(target, cfg);
                }
            } catch (Throwable e) {
                throw terminalError(e);
            }
            return capsule;
        }

		private static InitializationError terminalError(Throwable e) throws InitializationError {
			throw new InitializationError(Arrays.asList(e));
		}


		private static Properties loadXml(Class<?> enclosingClass, String path) throws IOException {
           final InputStream is    = enclosingClass.getResourceAsStream(path);
           final Properties props  = new Properties();
           try {
               props.loadFromXML(is);
           } catch (Exception e) {
               throw new RuntimeException(e);
           } finally {
               if(is  != null){
                   is.close();
               }
           }
           return props;
        }
    }
}
